﻿using System.Diagnostics;
using System.Runtime.InteropServices;
using RDF;
using System.Windows.Forms;
using System.Drawing;
using System.Linq;

using stepengine = RDF.ifcengine;

#if _WIN64
using int_t = System.Int64;
#else
using int_t = System.Int32;
#endif

namespace DZJIFC.Model
{
    public class IFCModelStructureView : IModelStructureView
    {
        #region Fields

        IController _controller;
        IFCModel _ifcModel;
        TreeView _treeView;
        ContextMenuStrip _contextMenuStrip;
        TreeNode _previousSelectedNode;

        Dictionary<string, IFCUnit> _dicUnits;

        #endregion // Fields

        #region Methods

        public IFCModelStructureView(IController controller, TreeView treeView, ContextMenuStrip contextMenuStrip)
        {
            Debug.Assert(controller != null);
            Debug.Assert(treeView != null);
            Debug.Assert(contextMenuStrip != null);

            _controller = controller;
            _ifcModel = controller.Model as IFCModel;
            _contextMenuStrip = contextMenuStrip;

            _treeView = treeView;
            _contextMenuStrip = contextMenuStrip;
        }

        #region IModelStructureView

        public void OnModelLoaded()
        {
            Debug.Assert(_controller.Model != null);

            _treeView.Nodes.Clear();
            _previousSelectedNode = null;
            _dicUnits = null;

            _ifcModel = _controller.Model as IFCModel;
            if (_ifcModel != null && !string.IsNullOrEmpty(_ifcModel.FilePath))
            {
                var tnModel = new IFCTreeNode(_ifcModel.FilePath, IFCTreeNodeType.unknown);
                tnModel.ContextMenuStrip = _contextMenuStrip;
                tnModel.ImageIndex = tnModel.SelectedImageIndex = IModelStructureView.IMAGE_CHECKED;
                _treeView.Nodes.Add(tnModel);

                LoadHeader(tnModel);
                LoadProjects(tnModel);
                LoadNotReferenced(tnModel);                

                tnModel.Expand();

                UpdateItemState(tnModel);
            }
        }

        public void OnInstanceSelected(object sender, IInstance instance)
        {
            if (sender == this)
            {
                return;
            }

            if (instance != null && instance.UIItem != null)
            {
                var ifcTreeNode = instance.UIItem as IFCTreeNode;
                Debug.Assert(ifcTreeNode != null);

                _treeView.SelectedNode = ifcTreeNode;
            }
            else
            {
                _treeView.SelectedNode = null;

                if (_previousSelectedNode != null)
                {
                    _previousSelectedNode.BackColor = _treeView.BackColor;
                }

                _previousSelectedNode = null;
            }
        }

        public void TreeView_OnNodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Rectangle rcIcon = new Rectangle(e.Node.Bounds.Location - new Size(16, 0), new Size(16, 16));
                if (!rcIcon.Contains(e.Location))
                {
                    return;
                }

                var ifcInstance = e.Node as IFCTreeNode;
                Debug.Assert(ifcInstance != null);

                switch (ifcInstance.ImageIndex)
                {
                    case IModelStructureView.IMAGE_CHECKED:
                    case IModelStructureView.IMAGE_MIDDLE:
                        {
                            ifcInstance.ImageIndex = ifcInstance.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;

                            // Geometry
                            if (ifcInstance.Type == IFCTreeNodeType.geometry)
                            {
                                Debug.Assert(ifcInstance.Parent != null);

                                var ifcParent = ifcInstance.Parent as IFCTreeNode;
                                Debug.Assert(ifcParent.Item != null);

                                ifcParent.Item.Enabled = false;

                                OnChildItemStateChanged(ifcInstance);
                            }
                            else
                            {
                                if (ifcInstance.Item != null)
                                {
                                    ifcInstance.Item.Enabled = false;
                                }

                                OnParentItemStateChanged(ifcInstance);
                                OnChildItemStateChanged(ifcInstance);
                            }
                        }
                        break;

                    case IModelStructureView.IMAGE_UNCHECKED:
                        {
                            ifcInstance.ImageIndex = ifcInstance.SelectedImageIndex = IModelStructureView.IMAGE_CHECKED;

                            if (ifcInstance.Type == IFCTreeNodeType.geometry)
                            {
                                Debug.Assert(ifcInstance.Parent != null);

                                var ifcParent = ifcInstance.Parent as IFCTreeNode;
                                Debug.Assert(ifcParent.Item != null);

                                ifcParent.Item.Enabled = true;

                                OnChildItemStateChanged(ifcInstance);
                            }
                            else
                            {
                                if (ifcInstance.Item != null)
                                {
                                    ifcInstance.Item.Enabled = true;
                                }

                                OnParentItemStateChanged(ifcInstance);
                                OnChildItemStateChanged(ifcInstance);
                            }
                        }
                        break;
                } // switch (ifcInstance.ImageIndex)
            } // if (e.Button == MouseButtons.Left)
            else
            {
                if (e.Button == MouseButtons.Right)
                {
                    _treeView.SelectedNode = e.Node;
                }
            }
        }

        public void TreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Tag != null)
            {
                IFCInstance ifcInstance = e.Node.Tag as IFCInstance;
                Debug.Assert(ifcInstance != null);

                _controller.SelectInstance(this, ifcInstance);

                if (_previousSelectedNode != null)
                {
                    _previousSelectedNode.BackColor = _treeView.BackColor;
                }

                _previousSelectedNode = e.Node;

                e.Node.BackColor = Color.Red;
            }
        }

        public void TreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            var ifcInstance = e.Node as IFCTreeNode;
            if (ifcInstance == null)
            {
                return;
            }

            if (ifcInstance.Nodes.Count != 1)
            {
                return;
            }

            if (ifcInstance.Nodes[0].Text != IModelStructureView.PROPERTIES_PENDING_LOAD)
            {
                return;
            }

            ifcInstance.Nodes.Clear();

            Debug.Assert(ifcInstance.Parent != null);

            var ifcParent = ifcInstance.Parent as IFCTreeNode;
            Debug.Assert(ifcParent != null);
            Debug.Assert(ifcParent.Item != null);

            stepengine.sdaiGetAttrBN(ifcParent.Item.Geometry.Instance, "IsDefinedBy", stepengine.sdaiAGGR, out IntPtr definedByInstances);
            Debug.Assert(definedByInstances != IntPtr.Zero);

            int_t iDefinedByCount = stepengine.sdaiGetMemberCount((int_t)definedByInstances);
            for (int_t iDefinedBy = 0; iDefinedBy < iDefinedByCount; iDefinedBy++)
            {
                int_t iDefinedByInstance = 0;
                stepengine.engiGetAggrElement((int_t)definedByInstances, iDefinedBy, stepengine.sdaiINSTANCE, out iDefinedByInstance);

                if (IsInstanceOf(iDefinedByInstance, "IFCRELDEFINESBYPROPERTIES"))
                {

                    LoadRelDefinesByProperties(ifcInstance, iDefinedByInstance);
                }
                else
                {
                    if (IsInstanceOf(iDefinedByInstance, "IFCRELDEFINESBYTYPE"))
                    {
                        //#todo - TBD
                        //LoadRelDefinesByType(iIFCIsDefinedByInstance, hParent);
                    }
                }
            }
        }

        public IInstance GetSelectedInstance()
        {
            var selectedNode = _treeView.SelectedNode;
            if (selectedNode.Tag != null)
            {
                var ifcInstance = selectedNode.Tag as IFCInstance;
                Debug.Assert(ifcInstance != null);

                return ifcInstance;
            }

            return null;
        }

        #endregion // IModelStructureView

        private void LoadHeader(IFCTreeNode tnModel)
        {
            // Header info
            TreeNode tnHeader = tnModel.Nodes.Add("Header");
            tnHeader.ImageIndex = tnHeader.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;

            // Descriptions
            TreeNode tnDescriptions = tnHeader.Nodes.Add("Descriptions");
            tnDescriptions.ImageIndex = tnDescriptions.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;

            int i = 0;
            while (stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 0, i++, stepengine.sdaiUNICODE, out IntPtr description) == 0)
            {
                TreeNode tnDescription = tnDescriptions.Nodes.Add(Marshal.PtrToStringUni(description));
                tnDescription.ImageIndex = tnDescription.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;
            }

            // ImplementationLevel
            stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 1, 0, stepengine.sdaiUNICODE, out IntPtr implementationLevel);

            TreeNode tnImplementationLevel = tnHeader.Nodes.Add("ImplementationLevel = '" + Marshal.PtrToStringUni(implementationLevel) + "'");
            tnImplementationLevel.ImageIndex = tnImplementationLevel.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;

            // Name
            stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 2, 0, stepengine.sdaiUNICODE, out IntPtr name);

            TreeNode tnName = tnHeader.Nodes.Add("Name = '" + Marshal.PtrToStringUni(name) + "'");
            tnName.ImageIndex = tnName.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;

            // TimeStamp
            stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 3, 0, stepengine.sdaiUNICODE, out IntPtr timeStamp);

            TreeNode tnTimeStamp = tnHeader.Nodes.Add("TimeStamp = '" + Marshal.PtrToStringUni(timeStamp) + "'");
            tnTimeStamp.ImageIndex = tnTimeStamp.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;

            // Authors
            TreeNode tnAuthors = tnHeader.Nodes.Add("Authors");
            tnAuthors.ImageIndex = tnAuthors.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;

            i = 0;
            while (stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 4, i++, stepengine.sdaiUNICODE, out IntPtr author) == 0)
            {
                TreeNode tnAuthor = tnAuthors.Nodes.Add(Marshal.PtrToStringUni(author));
                tnAuthor.ImageIndex = tnAuthor.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;
            }

            // Organizations
            TreeNode tnOrganizations = tnHeader.Nodes.Add("Organizations");
            tnOrganizations.ImageIndex = tnOrganizations.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;

            i = 0;
            while (stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 5, i++, stepengine.sdaiUNICODE, out IntPtr organization) == 0)
            {
                TreeNode tnOrganization = tnOrganizations.Nodes.Add(Marshal.PtrToStringUni(organization));
                tnOrganization.ImageIndex = tnOrganization.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;
            }

            // PreprocessorVersion
            stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 6, 0, stepengine.sdaiUNICODE, out IntPtr preprocessorVersion);

            TreeNode tnPreprocessorVersion = tnHeader.Nodes.Add("PreprocessorVersion = '" + Marshal.PtrToStringUni(preprocessorVersion) + "'");
            tnPreprocessorVersion.ImageIndex = tnPreprocessorVersion.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;

            // OriginatingSystem
            stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 7, 0, stepengine.sdaiUNICODE, out IntPtr originatingSystem);

            TreeNode tnOriginatingSystem = tnHeader.Nodes.Add("OriginatingSystem = '" + Marshal.PtrToStringUni(originatingSystem) + "'");
            tnOriginatingSystem.ImageIndex = tnOriginatingSystem.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;

            // Authorization
            stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 8, 0, stepengine.sdaiUNICODE, out IntPtr authorization);

            TreeNode tnAuthorization = tnHeader.Nodes.Add("Authorization = '" + Marshal.PtrToStringUni(authorization) + "'");
            tnAuthorization.ImageIndex = tnAuthorization.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;

            // FileSchemas
            TreeNode tnFileSchemas = tnHeader.Nodes.Add("FileSchemas");
            tnFileSchemas.ImageIndex = tnFileSchemas.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;

            i = 0;
            while (stepengine.GetSPFFHeaderItem(_ifcModel.Instance, 9, i++, stepengine.sdaiUNICODE, out IntPtr fileSchema) == 0)
            {
                TreeNode tnFileSchema = tnFileSchemas.Nodes.Add(Marshal.PtrToStringUni(fileSchema));
                tnFileSchema.ImageIndex = tnFileSchema.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;
            }
        }

        private void LoadProjects(IFCTreeNode tnModel)
        {
            int_t iEntityID = stepengine.sdaiGetEntityExtentBN(_ifcModel.Instance, "IfcProject");
            int_t iEntitiesCount = stepengine.sdaiGetMemberCount(iEntityID);

            for (int_t iEntity = 0; iEntity < iEntitiesCount; iEntity++)
            {
                int_t iInstance = 0;
                stepengine.engiGetAggrElement(iEntityID, iEntity, stepengine.sdaiINSTANCE, out iInstance);

                var tnProject = CreateIFCTreeNode(tnModel, iInstance);
                tnProject.ContextMenuStrip = _contextMenuStrip;

                /*
		        * decomposition/contains
		        */
                LoadIsDecomposedBy(tnProject, iInstance);
                LoadContainsElements(tnProject, iInstance);                

                /*
                 * Units
                 */
                _dicUnits = IFCUnit.LoadUnits(_ifcModel.Instance, iInstance);
                
                tnProject.Expand();

                // Support for 1 project only
                break;
            } // for (int_t iEntity = ...
        }

        private void LoadIsDecomposedBy(IFCTreeNode ifcParent, int_t iParentInstance)
        {
            // check for decomposition
            stepengine.sdaiGetAttrBN(iParentInstance, "IsDecomposedBy", stepengine.sdaiAGGR, out IntPtr decompositionInstance);

            if (decompositionInstance == IntPtr.Zero)
            {
                return;
            }

            int_t iDecompositionsCount = stepengine.sdaiGetMemberCount((int_t)decompositionInstance);
            if (iDecompositionsCount == 0)
            {
                return;
            }

            var ifcDecomposition = new IFCTreeNode(IModelStructureView.DECOMPOSITION, IFCTreeNodeType.decomposition);
            ifcDecomposition.ContextMenuStrip = _contextMenuStrip;
            ifcDecomposition.ImageIndex = ifcDecomposition.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;
            ifcParent.Nodes.Add(ifcDecomposition);

            for (int_t iDecomposition = 0; iDecomposition < iDecompositionsCount; iDecomposition++)
            {
                int_t iDecompositionInstance = 0;
                stepengine.engiGetAggrElement((int_t)decompositionInstance, iDecomposition, stepengine.sdaiINSTANCE, out iDecompositionInstance);

                if (!IsInstanceOf(iDecompositionInstance, "IFCRELAGGREGATES"))
                {
                    continue;
                }

                stepengine.sdaiGetAttrBN(iDecompositionInstance, "RelatedObjects", stepengine.sdaiAGGR, out IntPtr objectInstances);

                int_t iObjectsCount = stepengine.sdaiGetMemberCount((int_t)objectInstances);
                for (int_t iObject = 0; iObject < iObjectsCount; iObject++)
                {
                    int_t iObjectInstance = 0;
                    stepengine.engiGetAggrElement((int_t)objectInstances, iObject, stepengine.sdaiINSTANCE, out iObjectInstance);

                    var ifcTreeItem = CreateIFCTreeNode(ifcDecomposition, iObjectInstance);

                    /*
		            * decomposition/contains
		            */
                    LoadIsDecomposedBy(ifcTreeItem, iObjectInstance);
                    LoadContainsElements(ifcTreeItem, iObjectInstance);
                } // for (int_t iObject = ...
            } // for (int_t iDecomposition = ...
        }

        private void LoadContainsElements(IFCTreeNode ifcParent, int_t iParentInstance)
        {
            // check for elements
            stepengine.sdaiGetAttrBN(iParentInstance, "ContainsElements", stepengine.sdaiAGGR, out IntPtr elementsInstance);

            if (elementsInstance == IntPtr.Zero)
            {
                return;
            }

            int_t iElementsCount = stepengine.sdaiGetMemberCount((int_t)elementsInstance);
            if (iElementsCount == 0)
            {
                return;
            }

            var ifcContains = new IFCTreeNode(IModelStructureView.CONTAINS, IFCTreeNodeType.contains);
            ifcContains.ContextMenuStrip = _contextMenuStrip;
            ifcContains.ImageIndex = ifcContains.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;
            ifcParent.Nodes.Add(ifcContains);

            // Dictionary to group components by type
            var componentsByType = new Dictionary<string, List<int_t>>();

            for (int_t iElement = 0; iElement < iElementsCount; iElement++)
            {
                int_t iElementInstance = 0;
                stepengine.engiGetAggrElement((int_t)elementsInstance, iElement, stepengine.sdaiINSTANCE, out iElementInstance);

                if (!IsInstanceOf(iElementInstance, "IFCRELCONTAINEDINSPATIALSTRUCTURE"))
                {
                    continue;
                }

                stepengine.sdaiGetAttrBN(iElementInstance, "RelatedElements", stepengine.sdaiAGGR, out IntPtr objectInstances);

                int_t iObjectsCount = stepengine.sdaiGetMemberCount((int_t)objectInstances);
                for (int_t iObject = 0; iObject < iObjectsCount; iObject++)
                {
                    int_t iObjectInstance = 0;
                    stepengine.engiGetAggrElement((int_t)objectInstances, iObject, stepengine.sdaiINSTANCE, out iObjectInstance);

                    // Get the component type
                    string componentType = GetInstanceTypeName(iObjectInstance);
                    
                    // Group components by type
                    if (!componentsByType.ContainsKey(componentType))
                    {
                        componentsByType[componentType] = new List<int_t>();
                    }
                    componentsByType[componentType].Add(iObjectInstance);
                }
            }

            // Create type category nodes and add components under each type
            foreach (var typeGroup in componentsByType.OrderBy(x => x.Key))
            {
                string typeName = typeGroup.Key;
                var componentList = typeGroup.Value;
                
                // Get Chinese type name for better display
                string chineseTypeName = GetChineseTypeName(typeName);
                
                // Create type category node
                var ifcTypeCategory = new IFCTreeNode($"{chineseTypeName} ({typeName}) [{componentList.Count}]", IFCTreeNodeType.componentType);
                ifcTypeCategory.ContextMenuStrip = _contextMenuStrip;
                ifcTypeCategory.ImageIndex = ifcTypeCategory.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;
                ifcContains.Nodes.Add(ifcTypeCategory);

                // Add individual components under the type category
                foreach (var componentInstance in componentList)
                {
                    var ifcTreeItem = CreateIFCTreeNode(ifcTypeCategory, componentInstance);
                    if (!_ifcModel.IFCInstances.Keys.Contains(componentInstance))
                    {
                        ifcTreeItem.ImageIndex = ifcTreeItem.SelectedImageIndex = IModelStructureView.IMAGE_NOT_REFERENCED;
                    }

                    /*
                    * decomposition/contains for nested elements
                    */
                    LoadIsDecomposedBy(ifcTreeItem, componentInstance);
                    LoadContainsElements(ifcTreeItem, componentInstance);
                }
            }
        }

        /// <summary>
        /// Get the IFC type name of an instance
        /// 获取实例的IFC类型名称
        /// </summary>
        /// <param name="iInstance">Instance handle</param>
        /// <returns>IFC type name</returns>
        private string GetInstanceTypeName(int_t iInstance)
        {
            int_t iType = stepengine.sdaiGetInstanceType(iInstance);
            stepengine.engiGetEntityName(iType, stepengine.sdaiUNICODE, out IntPtr type);
            return Marshal.PtrToStringUni(type) ?? "Unknown";
        }

        /// <summary>
        /// Get Chinese translation of IFC type names
        /// 获取IFC类型名称的中文翻译
        /// </summary>
        /// <param name="ifcType">IFC type name</param>
        /// <returns>Chinese type name</returns>
        private string GetChineseTypeName(string ifcType)
        {
            return ifcType.ToUpper() switch
            {
                "IFCWALL" => "墙体",
                "IFCCOLUMN" => "柱子",
                "IFCBEAM" => "梁",
                "IFCSLAB" => "楼板",
                "IFCDOOR" => "门",
                "IFCWINDOW" => "窗",
                "IFCSTAIR" => "楼梯",
                "IFCRAILING" => "栏杆",
                "IFCROOF" => "屋顶",
                "IFCPLATE" => "板",
                "IFCMEMBER" => "构件",
                "IFCBUILDINGELEMENTPROXY" => "建筑构件代理",
                "IFCCURTAINWALL" => "幕墙",
                "IFCFOOTING" => "基础",
                "IFCSPACE" => "空间",
                "IFCBUILDINGSTOREY" => "楼层",
                "IFCBUILDING" => "建筑",
                "IFCSITE" => "场地",
                "IFCPROJECT" => "项目",
                "IFCRAMP" => "坡道",
                "IFCCOVERING" => "覆盖层",
                "IFCCEILING" => "吊顶",
                "IFCFLOWTERMINAL" => "流体终端",
                "IFCDISTRIBUTIONELEMENT" => "分配构件",
                "IFCFURNISHINGELEMENT" => "家具构件",
                "IFCTRANSPORTELEMENT" => "运输构件",
                "IFCVIRTUALELEMENT" => "虚拟构件",
                "IFCOPENINGELEMENT" => "开口构件",
                "IFCELECTRICALELEMENT" => "电气构件",
                "IFCEQUIPMENTELEMENT" => "设备构件",
                "IFCFEATUREELEMENT" => "特征构件",
                "IFCREINFORCINGBAR" => "钢筋",
                "IFCREINFORCINGMESH" => "钢筋网",
                "IFCTENDON" => "预应力筋",
                "IFCTENDONANCHOR" => "预应力锚具",
                _ => "其他构件"
            };
        }

        private void UpdateItemState(IFCTreeNode ifcParent)
        {
            Debug.Assert(ifcParent != null);

            int iItemsCount = 0;
            int iCheckedItems = 0;
            int iSemiCheckedItems = 0;
            foreach (var treeNode in ifcParent.Nodes)
            {
                var ifcInstance = treeNode as IFCTreeNode;
                if (ifcInstance == null ||
                    ifcInstance.Type == IFCTreeNodeType.properties ||
                    ifcInstance.Type == IFCTreeNodeType.property)
                {
                    continue;
                }

                UpdateItemState(ifcInstance);

                iItemsCount += ifcInstance.ImageIndex ==
                    IModelStructureView.IMAGE_CHECKED || ifcInstance.ImageIndex == IModelStructureView.IMAGE_MIDDLE || ifcInstance.ImageIndex == IModelStructureView.IMAGE_UNCHECKED ?
                    1 : 0;
                iCheckedItems += ifcInstance.ImageIndex == IModelStructureView.IMAGE_CHECKED ? 1 : 0;
                iSemiCheckedItems += ifcInstance.ImageIndex == IModelStructureView.IMAGE_MIDDLE ? 1 : 0;
            } // foreach (var treeNode ...

            if (iItemsCount > 0)
            {
                if (iSemiCheckedItems > 0)
                {
                    ifcParent.ImageIndex = ifcParent.SelectedImageIndex = IModelStructureView.IMAGE_MIDDLE;
                }
                else
                {
                    if (iCheckedItems > 0)
                    {
                        ifcParent.ImageIndex = ifcParent.SelectedImageIndex = iCheckedItems == iItemsCount ? IModelStructureView.IMAGE_CHECKED : IModelStructureView.IMAGE_MIDDLE;
                    }
                    else
                    {
                        ifcParent.ImageIndex = ifcParent.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;
                    }
                }
            } // if (iItemsCount > 0)
            else
            {
                if (ifcParent.Item != null)
                {
                    ifcParent.ImageIndex = ifcParent.SelectedImageIndex =
                        ifcParent.Item.Geometry.HasGeometry ?
                        ifcParent.Item.Enabled ? IModelStructureView.IMAGE_CHECKED : IModelStructureView.IMAGE_UNCHECKED :
                        IModelStructureView.IMAGE_NO_GEOMETRY;
                }
                else
                {
                    if (ifcParent.Type == IFCTreeNodeType.unknown ||
                        ifcParent.Type == IFCTreeNodeType.decomposition ||
                        ifcParent.Type == IFCTreeNodeType.contains ||
                        ifcParent.Type == IFCTreeNodeType.componentType ||
                        ifcParent.Type == IFCTreeNodeType.componentCategory ||
                        ifcParent.Type == IFCTreeNodeType.spaceView ||
                        ifcParent.Type == IFCTreeNodeType.space)
                    {
                        ifcParent.ImageIndex = ifcParent.SelectedImageIndex =
                            IModelStructureView.IMAGE_NO_GEOMETRY;
                    }
                    else
                    {
                        Debug.Assert(ifcParent.Type == IFCTreeNodeType.geometry);
                    }             
                }
            } // else if (iItemsCount > 0)
        }

        private bool HasProperties(int_t iInstance)
        {
            stepengine.sdaiGetAttrBN(iInstance, "IsDefinedBy", stepengine.sdaiAGGR, out IntPtr definedByInstances);

            if (definedByInstances == IntPtr.Zero)
            {
                return false;
            }

            int_t iDefinedByCount = stepengine.sdaiGetMemberCount((int_t)definedByInstances);
            for (int_t iDefinedBy = 0; iDefinedBy < iDefinedByCount; iDefinedBy++)
            {
                int_t iDefinedByInstance = 0;
                stepengine.engiGetAggrElement((int_t)definedByInstances, iDefinedBy, stepengine.sdaiINSTANCE, out iDefinedByInstance);

                if (IsInstanceOf(iDefinedByInstance, "IFCRELDEFINESBYPROPERTIES") || IsInstanceOf(iDefinedByInstance, "IFCRELDEFINESBYTYPE"))
                {
                    return true;
                }
            }

            return false;
        }

        private void LoadRelDefinesByProperties(IFCTreeNode ifcParent, int_t iInstance)
        {
            stepengine.sdaiGetAttrBN(iInstance, "RelatingPropertyDefinition", stepengine.sdaiINSTANCE, out IntPtr propertyInstances);

            if (IsInstanceOf((int_t)propertyInstances, "IFCELEMENTQUANTITY"))
            {
                var ifcPropertySetTreeItem = new IFCTreeNode(BuildItemText((int_t)propertyInstances), IFCTreeNodeType.properties);
                ifcPropertySetTreeItem.ContextMenuStrip = _contextMenuStrip;
                ifcPropertySetTreeItem.ImageIndex = ifcPropertySetTreeItem.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;
                ifcParent.Nodes.Add(ifcPropertySetTreeItem);

                // check for quantity
                stepengine.sdaiGetAttrBN((int_t)propertyInstances, "Quantities", stepengine.sdaiAGGR, out IntPtr quantitiesInstance);

                if (quantitiesInstance == IntPtr.Zero)
                {
                    return;
                }

                int_t iQuantitiesCount = stepengine.sdaiGetMemberCount((int_t)quantitiesInstance);
                for (int_t iQuantity = 0; iQuantity < iQuantitiesCount; iQuantity++)
                {
                    stepengine.engiGetAggrElement((int_t)quantitiesInstance, iQuantity, stepengine.sdaiINSTANCE, out int_t iQuantityInstance);

                    string strPropertyText = string.Empty;
                    if (IsInstanceOf(iQuantityInstance, "IFCQUANTITYLENGTH"))
                        strPropertyText = BuildPropertyText(iQuantityInstance, "IFCQUANTITYLENGTH");
                    else if (IsInstanceOf(iQuantityInstance, "IFCQUANTITYAREA"))
                        strPropertyText = BuildPropertyText(iQuantityInstance, "IFCQUANTITYAREA");
                    else if (IsInstanceOf(iQuantityInstance, "IFCQUANTITYVOLUME"))
                        strPropertyText = BuildPropertyText(iQuantityInstance, "IFCQUANTITYVOLUME");
                    else if (IsInstanceOf(iQuantityInstance, "IFCQUANTITYCOUNT"))
                        strPropertyText = BuildPropertyText(iQuantityInstance, "IFCQUANTITYCOUNT");
                    else if (IsInstanceOf(iQuantityInstance, "IFCQUANTITYWEIGTH"))
                        strPropertyText = BuildPropertyText(iQuantityInstance, "IFCQUANTITYWEIGTH");
                    else if (IsInstanceOf(iQuantityInstance, "IFCQUANTITYTIME"))
                        strPropertyText = BuildPropertyText(iQuantityInstance, "IFCQUANTITYTIME");

                    var ifcQuantityTreeItem = new IFCTreeNode(strPropertyText, IFCTreeNodeType.property);
                    ifcQuantityTreeItem.ContextMenuStrip = _contextMenuStrip;
                    ifcQuantityTreeItem.ImageIndex = ifcQuantityTreeItem.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;
                    ifcPropertySetTreeItem.Nodes.Add(ifcQuantityTreeItem);
                } // for (int iQuantity = ...
            }
            else
            {
                if (IsInstanceOf((int_t)propertyInstances, "IFCPROPERTYSET"))
                {
                    var ifcPropertySetTreeItem = new IFCTreeNode(BuildItemText((int_t)propertyInstances), IFCTreeNodeType.properties);
                    ifcPropertySetTreeItem.ContextMenuStrip = _contextMenuStrip;
                    ifcPropertySetTreeItem.ImageIndex = ifcPropertySetTreeItem.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;
                    ifcParent.Nodes.Add(ifcPropertySetTreeItem);

                    // check for quantity
                    stepengine.sdaiGetAttrBN((int_t)propertyInstances, "HasProperties", stepengine.sdaiAGGR, out IntPtr propertiesInstance);

                    if (propertiesInstance == IntPtr.Zero)
                    {
                        return;
                    }

                    int_t iPropertiesCount = stepengine.sdaiGetMemberCount((int_t)propertiesInstance);
                    for (int_t iProperty = 0; iProperty < iPropertiesCount; iProperty++)
                    {
                        int_t iPropertyInstance = 0;
                        stepengine.engiGetAggrElement((int_t)propertiesInstance, iProperty, stepengine.sdaiINSTANCE, out iPropertyInstance);

                        if (!IsInstanceOf(iPropertyInstance, "IFCPROPERTYSINGLEVALUE"))
                            continue;

                        var ifcPropertyTreeItem = new IFCTreeNode(BuildPropertyText(iPropertyInstance, "IFCPROPERTYSINGLEVALUE"), IFCTreeNodeType.property);
                        ifcPropertyTreeItem.ContextMenuStrip = _contextMenuStrip;
                        ifcPropertyTreeItem.ImageIndex = ifcPropertyTreeItem.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY;
                        ifcPropertySetTreeItem.Nodes.Add(ifcPropertyTreeItem);
                    } // for (int iProperty = ...
                }
            }
        }

        private bool IsInstanceOf(int_t iInstance, string strType)
        {
            if (stepengine.sdaiGetInstanceType(iInstance) == stepengine.sdaiGetEntity(_ifcModel.Instance, strType))
            {
                return true;
            }

            return false;
        }

        private void LoadNotReferenced(IFCTreeNode tnModel)
        {
            var tnNotReferenced = new IFCTreeNode("Unreferenced", IFCTreeNodeType.unknown);            
            tnNotReferenced.ContextMenuStrip = _contextMenuStrip;

            int iCheckedItemsCount = 0;
            foreach (var prInstance in _ifcModel.IFCInstances)
            {
                var ifcInstance = prInstance.Value;

                if (ifcInstance.UIItem != null)
                {
                    continue;
                }

                var ifcTreeItem = CreateIFCTreeNode(tnNotReferenced, ifcInstance.Instance);
                iCheckedItemsCount += ifcInstance.Enabled ? 1 : 0;
            } // foreach (var ifcInstance ...

            if (tnNotReferenced.Nodes.Count > 0)
            {
                if (iCheckedItemsCount > 0)
                {
                    tnNotReferenced.ImageIndex = tnNotReferenced.SelectedImageIndex = iCheckedItemsCount == tnNotReferenced.Nodes.Count ?
                        IModelStructureView.IMAGE_CHECKED : IModelStructureView.IMAGE_MIDDLE;
                }
                else
                {
                    tnNotReferenced.ImageIndex = tnNotReferenced.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;
                }

                tnModel.Nodes.Add(tnNotReferenced);
            }
        }

        private IFCTreeNode CreateIFCTreeNode(IFCTreeNode ifcParent, int_t iChildInstance)
        {
            var ifcChild = new IFCTreeNode(BuildItemText(iChildInstance), IFCTreeNodeType.item);
            ifcChild.ContextMenuStrip = _contextMenuStrip;

            // check the visual presentation & checked/unchecked state
            if (_ifcModel.IFCInstances.Keys.Contains(iChildInstance))
            {
                IFCInstance ifcInstance = _ifcModel.IFCInstances[iChildInstance];
                ifcChild.Tag = ifcInstance;
                ifcChild.Item = ifcInstance;
                ifcInstance.UIItem = ifcChild;
                ifcChild.ImageIndex = ifcChild.SelectedImageIndex = ifcInstance.Enabled ? IModelStructureView.IMAGE_CHECKED : IModelStructureView.IMAGE_UNCHECKED;

                // Geometry
                var ifcGeometry = new IFCTreeNode(IModelStructureView.GEOMETRY, IFCTreeNodeType.geometry);
                ifcGeometry.ContextMenuStrip = _contextMenuStrip;
                ifcGeometry.ImageIndex = ifcGeometry.SelectedImageIndex = ifcInstance.HasGeometry ?
                    ifcInstance.Enabled ? IModelStructureView.IMAGE_CHECKED : IModelStructureView.IMAGE_UNCHECKED :
                    IModelStructureView.IMAGE_NO_GEOMETRY;
                ifcChild.Nodes.Add(ifcGeometry);

                // Properties
                if (HasProperties(iChildInstance))
                {
                    var ifcProperties = new IFCTreeNode(IModelStructureView.PROPERTIES, IFCTreeNodeType.properties);
                    ifcProperties.ContextMenuStrip = _contextMenuStrip;
                    ifcProperties.ImageIndex = ifcProperties.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;

                    ifcChild.Nodes.Add(ifcProperties);

                    // Load on demand
                    var ifcLoadOnDemand = new IFCTreeNode(IModelStructureView.PROPERTIES_PENDING_LOAD, IFCTreeNodeType.unknown);
                    ifcProperties.Nodes.Add(ifcLoadOnDemand);
                }
            }
            else
            {
                ifcChild.ImageIndex = ifcChild.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;

                // Geometry
                var ifcGeometry = new IFCTreeNode(IModelStructureView.GEOMETRY, IFCTreeNodeType.geometry);
                ifcGeometry.ContextMenuStrip = _contextMenuStrip;
                ifcGeometry.ImageIndex = ifcGeometry.SelectedImageIndex = IModelStructureView.IMAGE_NOT_REFERENCED;
                ifcChild.Nodes.Add(ifcGeometry);

                // Properties
                if (HasProperties(iChildInstance))
                {
                    var ifcProperties = new IFCTreeNode(IModelStructureView.PROPERTIES, IFCTreeNodeType.properties);
                    ifcProperties.ContextMenuStrip = _contextMenuStrip;
                    ifcProperties.ImageIndex = ifcProperties.SelectedImageIndex = IModelStructureView.IMAGE_PROPERTY_SET;

                    ifcChild.Nodes.Add(ifcProperties);

                    // Load on demand
                    var ifcLoadOnDemand = new IFCTreeNode(IModelStructureView.PROPERTIES_PENDING_LOAD, IFCTreeNodeType.unknown);
                    ifcProperties.Nodes.Add(ifcLoadOnDemand);
                }
            }

            if (ifcParent != null)
            {
                ifcParent.Nodes.Add(ifcChild);
            }

            return ifcChild;
        }

        private void OnParentItemStateChanged(IFCTreeNode ifcParent)
        {
            Debug.Assert(ifcParent != null);

            foreach (var treeNode in ifcParent.Nodes)
            {
                var ifcInstance = treeNode as IFCTreeNode;
                if (ifcInstance == null ||
                    ifcInstance.Type == IFCTreeNodeType.properties ||
                    ifcInstance.Type == IFCTreeNodeType.property)
                {
                    continue;
                }

                if (ifcInstance.ImageIndex == IModelStructureView.IMAGE_NO_GEOMETRY)
                {
                    // Keep the image

                    if (ifcInstance.Item != null)
                    {
                        ifcInstance.Item.Enabled = false;
                    }

                    // Do not update children
                    continue;
                }

                switch (ifcParent.ImageIndex)
                {
                    case IModelStructureView.IMAGE_CHECKED:
                        {
                            ifcInstance.ImageIndex = ifcInstance.SelectedImageIndex = IModelStructureView.IMAGE_CHECKED;

                            if (ifcInstance.Item != null)
                            {
                                ifcInstance.Item.Enabled = true;
                            }

                        }
                        break;

                    case IModelStructureView.IMAGE_UNCHECKED:
                        {
                            ifcInstance.ImageIndex = ifcInstance.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;

                            if (ifcInstance.Item != null)
                            {
                                ifcInstance.Item.Enabled = false;
                            }
                        }
                        break;

                    default:
                        {
                            Debug.Assert(false); // unexpected
                        }
                        break;
                } // switch (ifcParent.ImageIndex)

                OnParentItemStateChanged(ifcInstance);
            } // foreach (var treeNode ...
        }

        private void OnChildItemStateChanged(IFCTreeNode? ifcChild)
        {
            if (ifcChild == null)
            {
                throw new ArgumentNullException();
            }

            if (ifcChild.Parent == null)
            {
                return;
            }

            int iItemsCount = 0;
            int iCheckedItems = 0;
            int iSemiCheckedItems = 0;
            foreach (var treeNode in ifcChild.Parent.Nodes)
            {
                var ifcInstance = treeNode as IFCTreeNode;
                if (ifcInstance == null ||
                    ifcInstance.Type == IFCTreeNodeType.properties ||
                    ifcInstance.Type == IFCTreeNodeType.property)
                {
                    continue;
                }

                iItemsCount += ifcInstance.ImageIndex == 
                    IModelStructureView.IMAGE_CHECKED || ifcInstance.ImageIndex == IModelStructureView.IMAGE_MIDDLE || ifcInstance.ImageIndex == IModelStructureView.IMAGE_UNCHECKED 
                    ? 1 : 0;
                iCheckedItems += ifcInstance.ImageIndex == IModelStructureView.IMAGE_CHECKED ? 1 : 0;
                iSemiCheckedItems += ifcInstance.ImageIndex == IModelStructureView.IMAGE_MIDDLE ? 1 : 0;
            } // foreach (var treeNode ...

            // Update the state only if the item has children; otherwise keep the original state
            if (iItemsCount > 0)
            {
                if (iSemiCheckedItems > 0)
                {
                    ifcChild.Parent.ImageIndex = ifcChild.Parent.SelectedImageIndex = IModelStructureView.IMAGE_MIDDLE;
                }
                else
                {
                    if (iCheckedItems > 0)
                    {
                        ifcChild.Parent.ImageIndex = ifcChild.Parent.SelectedImageIndex = 
                            iCheckedItems == iItemsCount ? 
                            IModelStructureView.IMAGE_CHECKED : 
                            IModelStructureView.IMAGE_MIDDLE;
                    }
                    else
                    {
                        ifcChild.Parent.ImageIndex = ifcChild.Parent.SelectedImageIndex = IModelStructureView.IMAGE_UNCHECKED;
                    }
                }
            } // if (iItemsCount > 0)
            
            if (ifcChild.Parent.Parent != null)
            {
                OnChildItemStateChanged(ifcChild.Parent as IFCTreeNode);
            }
        }

        private string BuildItemText(int_t iInstance)
        {
            if (iInstance == 0)
            {
                Debug.Assert(false);

                return string.Empty;
            }

            long iExpressId = stepengine.internalGetP21Line(iInstance);

            int_t iType = stepengine.sdaiGetInstanceType(iInstance);
            stepengine.engiGetEntityName(iType, stepengine.sdaiUNICODE, out IntPtr type);
            string? strIfcType = Marshal.PtrToStringUni(type);

            stepengine.sdaiGetAttrBN(iInstance, "Name", stepengine.sdaiUNICODE, out IntPtr name);
            string? strName = Marshal.PtrToStringUni(name);

            stepengine.sdaiGetAttrBN(iInstance, "Description", stepengine.sdaiUNICODE, out IntPtr description);
            string? strDescription = Marshal.PtrToStringUni(description);

            string strItemText = iExpressId != 0 ? string.Format("#{0} ", iExpressId) : string.Empty;
            strItemText += !string.IsNullOrEmpty(strIfcType) ? strIfcType : "NA";
            if (!string.IsNullOrEmpty(strName))
            {
                strItemText += " '";
                strItemText += strName;
                strItemText += "'";
            }

            if (!string.IsNullOrEmpty(strDescription))
            {
                strItemText += " (";
                strItemText += strDescription;
                strItemText += ")";
            }

            return strItemText;
        }

        private string BuildPropertyText(int_t iInstance, string strProperty)
        {
            if (iInstance == 0)
            {
                Debug.Assert(false);

                return string.Empty;
            }

            int_t entity = stepengine.sdaiGetInstanceType(iInstance);

            stepengine.engiGetEntityName(entity, stepengine.sdaiUNICODE, out IntPtr entityNamePtr);

            string strIfcType = Marshal.PtrToStringUni(entityNamePtr);

            stepengine.sdaiGetAttrBN(iInstance, "Name", stepengine.sdaiUNICODE, out IntPtr name);

            string strName = Marshal.PtrToStringUni(name);

            string strUnit = string.Empty;

            stepengine.sdaiGetAttrBN(iInstance, "Unit", stepengine.sdaiUNICODE, out IntPtr unit);

            if (unit != IntPtr.Zero)
            {
                strUnit = Marshal.PtrToStringUni(unit);
            }

            string strValue = string.Empty;
            switch (strProperty)
            {
                case "IFCQUANTITYLENGTH":
                    {
                        stepengine.sdaiGetAttrBN(iInstance, "LengthValue", stepengine.sdaiUNICODE, out IntPtr value);

                        strValue = Marshal.PtrToStringUni(value);

                        if (string.IsNullOrEmpty(strUnit))
                        {
                            if (_dicUnits.Keys.Contains("LENGTHUNIT"))
                            {
                                strUnit = _dicUnits["LENGTHUNIT"].Name;
                            }
                        }
                    }
                    break;

                case "IFCQUANTITYAREA":
                    {
                        stepengine.sdaiGetAttrBN(iInstance, "AreaValue", stepengine.sdaiUNICODE, out IntPtr value);

                        strValue = Marshal.PtrToStringUni(value);

                        if (string.IsNullOrEmpty(strUnit))
                        {
                            if (_dicUnits.Keys.Contains("AREAUNIT"))
                            {
                                strUnit = _dicUnits["AREAUNIT"].Name;
                            }
                        }
                    }
                    break;

                case "IFCQUANTITYVOLUME":
                    {
                        stepengine.sdaiGetAttrBN(iInstance, "VolumeValue", stepengine.sdaiUNICODE, out IntPtr value);

                        strValue = Marshal.PtrToStringUni(value);

                        if (string.IsNullOrEmpty(strUnit))
                        {
                            if (_dicUnits.Keys.Contains("VOLUMEUNIT"))
                            {
                                strUnit = _dicUnits["VOLUMEUNIT"].Name;
                            }
                        }
                    }
                    break;

                case "IFCQUANTITYCOUNT":
                    {
                        stepengine.sdaiGetAttrBN(iInstance, "CountValue", stepengine.sdaiUNICODE, out IntPtr value);

                        strValue = Marshal.PtrToStringUni(value);
                    }
                    break;

                case "IFCQUANTITYWEIGTH":
                    {
                        stepengine.sdaiGetAttrBN(iInstance, "WeigthValue", stepengine.sdaiUNICODE, out IntPtr value);

                        strValue = Marshal.PtrToStringUni(value);

                        if (string.IsNullOrEmpty(strUnit))
                        {
                            if (_dicUnits.Keys.Contains("MASSUNIT"))
                            {
                                strUnit = _dicUnits["MASSUNIT"].Name;
                            }
                        }
                    }
                    break;

                case "IFCQUANTITYTIME":
                    {
                        stepengine.sdaiGetAttrBN(iInstance, "TimeValue", stepengine.sdaiUNICODE, out IntPtr value);

                        strValue = Marshal.PtrToStringUni(value);

                        if (string.IsNullOrEmpty(strUnit))
                        {
                            if (_dicUnits.Keys.Contains("TIMEUNIT"))
                            {
                                strUnit = _dicUnits["TIMEUNIT"].Name;
                            }
                        }
                    }
                    break;

                case "IFCPROPERTYSINGLEVALUE":
                    {
                        stepengine.sdaiGetAttrBN(iInstance, "NominalValue", stepengine.sdaiUNICODE, out IntPtr value);

                        strValue = Marshal.PtrToStringUni(value);
                    }
                    break;

                default:
                    throw new Exception("Unknown property.");
            } // switch (strProperty)    

            string strItemText = "'" + (string.IsNullOrEmpty(strName) ? "<name>" : strName) +
                "' = '" + (string.IsNullOrEmpty(strValue) ? "<value>" : strValue) +
                (!string.IsNullOrEmpty(strUnit) ? " " + strUnit : "") +
                "' (" + strIfcType + ")";

            return strItemText;
        }

        #endregion // Methods
    }
}
